# coding: utf-8
# 19-10-29 下午3:52

import copy
import datetime
import hashlib
import logging

from django.contrib.auth.models import User
from django.core.cache import cache
from django.http.response import HttpResponse
from django.utils.deprecation import MiddlewareMixin
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication

from main import config
from main.apps.purification.models import Customer
from main.common.decorators import decorate_cache
from main.common.redis.redis_keys import (CLOSE_EXTRA_VERIFY,
                                          GET_USER_ALL_APPKEY)

logger = logging.getLogger('django')  # 这里暂时使用默认的

APP_AUTH = 'app'


@decorate_cache(GET_USER_ALL_APPKEY)
def get_user_all_appkey():
    res = Customer.objects.values('code', 'app_key')  # todo get_user_all_appkey  # 客户登录
    ret = {item['code']: item['app_key'] for item in res}
    return ret


class ApiAuth(BaseAuthentication):
    expire = getattr(config, 'DEFAULT_API_EXPIRE', 5 * 60)  # 时间过期
    encrypt_times = getattr(config, 'DEFAULT_API_ENCERYPT_TIMES', 1)
    salt = copy.deepcopy(getattr(config, 'API_SALTS', {}))

    check_params = []

    @staticmethod
    def check_fields(token, timestamp):
        if all((token, timestamp)):
            return True
        return False

    def check_timestamp(self, timestamp):
        try:
            time = datetime.datetime.fromtimestamp(int(timestamp))
        except ValueError:
            return False
        return abs((datetime.datetime.now() - time).total_seconds()) < self.expire

    @staticmethod
    def encrypt(plain):
        return hashlib.md5(plain).hexdigest()

    def get_salt(self, name):
        salt = self.salt.get(name)
        if not salt:
            self.salt = copy.deepcopy(getattr(config, 'API_SALTS', {}))  # 更新app_key
            tmp_key = get_user_all_appkey()  # 保证settings配置中的key不被覆盖
            tmp_key.update(self.salt)
            self.salt = tmp_key
            salt = self.salt.get(name)
            if not salt or salt == 'dummy':
                raise ValueError('Salt cannot be "dummy"')
        return salt

    def get_token(self, timestamp, name='app', addition=''):
        salt = self.get_salt(name)
        if not salt:
            logger.debug('No such user %s' % name)
            return False
        to_encrypt = '%s%s%s' % (timestamp, addition, salt)
        to_encrypt = to_encrypt.encode('utf-8')
        for _ in range(self.encrypt_times):
            to_encrypt = self.encrypt(to_encrypt)
        return to_encrypt

    def check_token(self, name, token, timestamp, addition=''):
        true_token = self.get_token(timestamp, name, addition)
        match = true_token == token
        if not match:
            logger.info('Token not match, should be %s' % true_token)
        return match

    def authenticate(self, request):
        name = request.GET.get('n', APP_AUTH)
        token = request.GET.get('t')
        if config.DEBUG:  # 测试环境不验证
            return User.objects.filter(username=name).first(), None

        try:
            timestamp = int(float(request.GET.get('s')))
        except Exception as e:
            print(e.args)
            raise exceptions.AuthenticationFailed(detail='时间戳错误')

        addition = ''.join([request.GET.get(p, '') for p in self.check_params])

        if not self.check_fields(token, timestamp):
            raise exceptions.AuthenticationFailed(detail='token, timestamp必须同时存在')

        if not self.check_timestamp(timestamp):
            raise exceptions.AuthenticationFailed(detail='时间戳已过期')

        if not self.check_token(name, token, timestamp, addition):
            raise exceptions.AuthenticationFailed(detail='token不匹配,请重新加密')

        return User.objects.filter(username=name).first() if name != APP_AUTH else name, None


class ExtraVerifyMiddleware(MiddlewareMixin):
    """
    app端的接口(包含/app/)增加额外验证
    """

    def process_request(self, request):
        if not config.ENV.startswith('pro') and cache.get(CLOSE_EXTRA_VERIFY):
            return
        if '/app/' in request.path and '/version/' not in request.path:
            try:
                ApiAuth().authenticate(request)
            except exceptions.AuthenticationFailed as e:
                return HttpResponse(e.detail, status=403)
            except Exception as e:
                return HttpResponse(e.args, status=403)
